<?php
/**
 * NetBiz Framework
 * Copyright (C) 20009 - 2010 NetBiz
 * 
 * All rights reserved.
 */

class FRM_DateTime
{
	static protected $_loc_months = array();
	static protected $_loc_days = array();

	static public function initLocaleWords()
	{
		foreach(
			array('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec') as $k => $m)
		{
			list($full,$short) = explode('_',strftime('%B_%b',strtotime($m)));
			self::$_loc_months['short'][$k+1] = $short;
			self::$_loc_months['full'][$k+1] = $full;
		}

		foreach(array('mon','tue','wed','thu','fri','sat','sun') as $k => $d)
		{
			list($full,$short) = explode('_',strftime('%A_%a',strtotime($d)));
			self::$_loc_days['short'][$k+1] = $short;
			self::$_loc_days['full'][$k+1] = $full;
		}
	}

	/**
	 * Return true if the date given in parameters is in correct mysql format
	 * which is 'Y-m-d', false otherwise
	 *
	 * @access public
	 * @param string $date_string the date string to validate against mysql date format
	 * @return boolean true if the format is valid, false otherwise
	*/	
	static public function isDateSql($date_string)
	{
		$date_array = explode('-',$date_string);
		if (count($date_array) == 3)
		{
			return checkdate($date_array[1],$date_array[2],$date_array[0]);
		}
		return false;
	}

	/**
	 * Return true if the time string given in parameters is in correct mysql format
	 * which is 'H:i:s' or 'H:i', false otherwise
	 *
	 * @access public
	 * @param string the time string to validate against mysql time format
	 * @return boolean true if the format is valid, false otherwise
	*/
	static public function isTimeSql($time_string)
	{
		$time_array = explode(':',$time_string);
		if ( count($time_array) >= 2 or count($time_array) <= 3 )
		{
			return 
				// Hour validation
				preg_match('/^[0-2]?[0-9]$/',$time_array[0]) and intval($time_array[0]) <= 24 and
				// Minute validation
				preg_match('/^[0-5]?[0-9]$/',$time_array[1]) and
				// second validation
				( !isset($time_array[2]) or preg_match('/^[0-5]?[0-9]$/',$time_array[2]) );
		}
		return false;
	}

	/**
	 * Return true if the datetime string given in parameters is in correct mysql format
	 * which is 'Y-m-d H:i:s' or 'Y-m-d H:i', false otherwise
	 *
	 * @access public
	 * @param string the time string to validate against mysql datetime format
	 * @return boolean true if the format is valid, false otherwise
	*/
	static public function isDateTimeSql($date_time_string)
	{
		$date_time_array = explode(' ',$date_time_string);
		if ( count($ate_time_array) == 2 )
		{
			return self::isDateSql($date_time_array[0]) and self::isTimeSql($date_time_array[1]);
		}
		else
		{
			return false;
		}
	}

	/*function date_to_date_sql($date_convert, $format="dd/mm/yyyy")
	{
		$pos = strpos($date_convert, '/');
		if ($pos === false)
			return $date_convert;
		
		switch ($format) {
			case "dd/mm/yyyy":
				list($day, $month, $year) = explode("/", $date_convert);
				break;
			case "mm/dd/yyyy":
				list($month, $day, $year) = explode("/", $date_convert);
				break;
		}

		if (($year == "1970") && ($month == "01" || $month == "1") && ($day == "01" || $day == "1"))
			return "1970-01-01";
		if (checkdate(intval($month), intval($day), intval($year)))
			return date("Y-m-d", mktime(0, 0, 0, intval($month), intval($day), intval($year)));
	
		return $date_convert;
	}
	
	function datetime_to_datetimesql($datetime_convert, $format="dd/mm/yyyy")
	{
		$date = $this->date_to_date_sql(substr($datetime_convert, 0, 10), $format);
		return $date . substr($datetime_convert, 10);
	}
	
	function getDiffTime($time_from, $time_to, $format='dhms')
	{
		$tmp_1=strtotime($time_from);
		$tmp_2=strtotime($time_to);
		$distance=$tmp_2-$tmp_1;
		
		switch ($format) {
			case 'd':
				$day=floor($distance / 86400);
				return "$day d";
				break;
			case 'h':
				$hour=floor($distance / 3600 );
				return "$hour h";
				break;
			case 'm':
				$min=floor($distance / 60 );
				return "$min m";
				break;
			case 's':
				return "$distance s";
				break;
			case 'array':
			case 'dhms':
			default:
				$day=floor($distance / 86400);
				$distance=$distance % 86400;
				$hour=floor($distance / 3600 );
				$distance=$distance % 3600;
				$min=floor($distance / 60 );
				$s=$distance % 60;
				
				if ($format == 'array')	{
					return array("d" => $day, "h" => $hour, "m" => $min, "s" => $s);
				} else {
					return "$day d $hour h $min m $s s";
				}
		}
	}*/
	
	/**
     * Get a string (or other) representation of the date
     *
     * Get a string (or other) representation of this date in the
     * format specified by the DATE_FORMAT_* constants.
     *
     * @access public
     * @param int $format format constant (DATE_FORMAT_*) of the output date
     * @return string the date in the requested format
     */
    /*function getDate($format = DATE_FORMAT_ISO)
    {
        switch ($format) {
	        case DATE_FORMAT_ISO:
	            return $this->format("%Y-%m-%d %T");
	            break;
	        case DATE_FORMAT_ISO_BASIC:
	            $format = "%Y%m%dT%H%M%S";
	            if ($this->tz->getID() == 'UTC') {
	                $format .= "Z";
	            }
	            return $this->format($format);
	            break;
	        case DATE_FORMAT_ISO_EXTENDED:
	            $format = "%Y-%m-%dT%H:%M:%S";
	            if ($this->tz->getID() == 'UTC') {
	                $format .= "Z";
	            }
	            return $this->format($format);
	            break;
	        case DATE_FORMAT_ISO_EXTENDED_MICROTIME:
	            $format = "%Y-%m-%dT%H:%M:%s";
	            if ($this->tz->getID() == 'UTC') {
	                $format .= "Z";
	            }
	            return $this->format($format);
	            break;
	        case DATE_FORMAT_TIMESTAMP:
	            return $this->format("%Y%m%d%H%M%S");
	            break;
	        case DATE_FORMAT_UNIXTIME:
	            return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
	            break;
	        default:
	        	return $this->format($format);
		}
    }*/

	/**
	 * Parse a date string according to a format and return
	 * a date structure representing the string.
	 *
	 * The format is represented in a subset of the standard php
	 * date format ( @link http://php.net/date ), containing :
	 * (d,j,m,n,y,Y,a,A,g,G,h,H,i,s,F,M,D,l) + some specials (f,S)
	 * All date characters must be prefixed by a % sign (like %a, %y)
	 *
	 * The return structure is an associative array containing the
	 * following keys :
	 *   - year, month, day
	 *   - hour, minute, second
	 *
	 * If not specified, the default value for each of these key is
	 * taken from the default date : 1900-01-01 00:00:00
	 *
	 * If the format validation fail or the date is invalid, FALSE is returned
	 *
	 * @param string the date string to parse
	 * @param string the format in which the date string is expected, if NULL
	 *   the default input format is used
	 * @return mixed a date array structure if success, FALSE otherwise
	 */
	static public function parseDate($date_str,$format = null)
	{
		// Default format if null
		$format = is_null($format) ? '%Y-%m-%d%[ %H%[:%i%[:%s%]%]%]' : $format;

		// Construct the default date structure
		$date = array('year' => 1900, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0);

		// Transform the format string in a pregexp
		$preg_format = preg_replace(
			array(
				// '/[\(\)\{\}\[\]\?\.\*\+\/\\\\]/', // special regexp characters
				'/%d/', // day of month with leading 0
				'/%j/', // day of month without leading 0
				'/%[mh]/', // month with leading 0 and 12-hour format of an hour with leading zeros
				'/%[ng]/', // month without leading 0 and 12-hour format of an hour without leading zeros
				'/%Y/', // year on 4 digit
				'/%y/', // year on 2 digit
				'/%[is]/', // minute and second with leading 0
				'/%G/', // 24-hour format of an hour without leading zeros
				'/%H/', // 24-hour format of an hour with leading zeros
				'/%a/', // Lowercase Ante meridiem and Post meridiem
				'/%A/', // Uppercase Ante meridiem and Post meridiem
				'/%f/', // Year on infinite number of digit, no leading zeros
				'/%S/', // Year sign (- or + or nothing)
				'/%\\\\\[/', // Start of optionnal group
				'/%\\\\\]/', // End of optionnal group
				'/%F/', // Full representation of the month
				'/%M/', // short representation of the month
				'/%l/', // Full representation of the day of the weel
				'/%D/', // short representation of the day of the weel
				'/%%/'
				),
			array(
				// '\\\\$0', // special regexp characters
				'([0-3]\d)', // day of month with leading 0
				'([1-3]?\d)', // day of month without leading 0
				'([0-1]\d)', // month with leading 0 and 12-hour format of an hour with leading zeros
				'(1?\d)', // month without leading 0 and 12-hour format of an hour without leading zeros
				'(\d{4,})', // year on 4 digit
				'(\d{2})', // year on 2 digit
				'([0-5]\d)', // minute and second with leading 0
				'([1-2]?\d)', // 24-hour format of an hour without leading zeros
				'([0-2]\d)', // 24-hour format of an hour with leading zeros
				'([ap]m)', // Lowercase Ante meridiem and Post meridiem
				'([AP]M)', // Uppercase Ante meridiem and Post meridiem
				'([1-9]\d*)', // Year on infinite number of digit, no leading zeros
				'((?:\-|\+)?)', // Year sign (- or + or nothing)
				'(',
				')?',
				'('.implode('|',self::$_loc_months['full']).')',
				'('.implode('|',self::$_loc_months['short']).')',
				'('.implode('|',self::$_loc_days['full']).')',
				'('.implode('|',self::$_loc_days['short']).')',
				'%'
				),
			preg_quote($format,'/'));

		// We get the list of format modifier in a simple matches array
		$count = preg_match_all('/%([djmhngYyisGHaAFMlDSf\[])/',$format,$modifiers);
		$count = preg_match_all('/^'.$preg_format.'$/',$date_str,$matches);

		// If there's no match, we know the date is in an invalid format, we return FALSE
		if ($count == 0)
		{
			return FALSE;
		}

		// Match modifier with their values
		$format_arr = array();
		$index = 1;
		foreach($modifiers[1] as $modifier)
		{
			if ($modifier == 'A')
			{
				$format_arr['a'] = strtolower($matches[$index][0]);
			}
			else
			{
				$format_arr[$modifier] = $matches[$index][0];
			}
			++$index;
		}

		foreach($format_arr as $mod => $val)
		{
				switch($mod)
				{
					case 'y' :
						$date['year'] = 1900+intval($val);
						if (intval($val) < 70)
						{
							$date['year'] += 100;
						}
						break;
					case 'Y' :
						$date['year'] = intval($val);
						break;
					case 'm' :
					case 'n' :
						$date['month'] = intval($val);
						break;
					case 'F' :
						$date['month'] = array_search($val,self::$_loc_months['full']);
						break;
					case 'M' :
						$date['month'] = array_search($val,self::$_loc_months['short']);
						break;
					case 'd' :
					case 'j' :
						$date['day'] = intval($val);
						break;
					case 'i' :
						$date['minute'] = intval($val);
						break;
					case 's' :
						$date['second'] = intval($val);
						break;
					case 'g' :
					case 'h' :
						$hour = intval($val);
						if ($hour >= 1 and $hour <= 12)
						{
							//$hour %= $hour;
							if (isset($format_arr['a']) and $format_arr['a'] == 'pm')
							{
								$hour += 12; 
							}
							$date['hour'] = $hour;
						}
						else
						{
							return FALSE;
						}
						break;
					case 'G' :
					case 'H' :
						$hour = intval($val);
						if ($hour >= 0 and $hour <= 23)
						{
							$date['hour'] = $hour;
						}
						else
						{
							return FALSE;
						}
						break;
					case 'F' :
						$date['year'] = intval($val);
						if (isset($format_arr['S']) and $format_arr['S'] == '-')
						{
							$date['year'] = -$date['year'];
						}
						break;
					default :
						break;
				}
			
		}

		// Now validate the structure, then return it if it's ok
		// We cannot give a negative number to checkdate, so we make
		// a negative  to positive modulo to obtain the equivalent positive
		// year (on a leap year based point of view)
		if (checkdate($date['month'],$date['day'],($date['year']>0 ? $date['year'] : $date['year'] % 400 + 400 )))
		{
			return $date;
		}
		else
		{
			return FALSE;
		}
	}

	/**
	 * Format a date structure according to a format and return
	 * a date string.
	 *
	 * The format is represented in a subset of the standard php
	 * date format ( @link http://php.net/date ), containing :
	 * (d,j,m,n,y,Y,a,A,g,G,h,H,i,s,F,M,D,l) + some specials (f,S)
	 * All date characters must be prefixed by a % sign (like %a, %y)
	 *
	 * The date structure is an associative array containing the
	 * following keys :
	 *   - year, month, day
	 *   - hour, minute, second
	 *
	 * If not specified, the default value for each of these key is
	 * taken from the default date : 1900-01-01 00:00:00
	 *
	 * @param array the date structure to format
	 * @param string the format in which the return string must be returned
	 * @return string the formatted date string
	 */
	static public function formatDate($date,$format)
	{
		// Construct the date structure
		$date_new = array('year' => 1900, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0);
		$date = array_merge($date_new,$date);

		// Calculate day of week, only for epoch compatible date
		// :TODO: implement calculation from 1 to xx
		// using mixed julian and gregorian calendar, but only
		// if the timestamping fail
		$date['N'] = date('N',mktime(0,0,0,$date['month'],$date['day'],$date['year']));
		$date['l'] = self::$_loc_days['full'][$date['N']];
		$date['D'] = self::$_loc_days['short'][$date['N']];

		$date['d'] = str_pad(strval($date['day']),2,'0',STR_PAD_LEFT);
		$date['m'] = str_pad(strval($date['month']),2,'0',STR_PAD_LEFT);
		$date['F'] = self::$_loc_months['full'][$date['month']];
		$date['M'] = self::$_loc_months['short'][$date['month']];

		if ($date['year'] < 0)
		{
			$date['Y'] = 0;
		}
		elseif ($date['year'] > 9999)
		{
			$date['Y'] = 9999;
		}
		else
		{
			$date['Y'] = str_pad(strval($date['year']),4,'0',STR_PAD_LEFT);
		}
		if ($date['year'] >= 1970 and $date['year'] <= 1999)
		{
			$date['y'] = strval($date['year'] - 1900);
		}
		elseif ($date['year'] >= 2000 and $date['year'] <= 2069)
		{
			$date['y'] = str_pad(strval($date['year'] - 2000),2,'0',STR_PAD_LEFT);
		}
		else
		{
			$date['y'] = '70';
		}
		$date['a'] = ($date['hour'] < 12 ? 'am' : 'pm' );
		$date['A'] = strtoupper($date['a']);
		if ($date['hour'] >= 12)
		{
			$date['g'] = $date['hour'] - 12;
		}
		else
		{
			$date['g'] = $date['hour'];
		}
		if ($date['g'] == 0)
		{
			$date['g'] = '12';
		}

		$date['h'] = str_pad($date['g'],2,'0',STR_PAD_LEFT);
		$date['H'] = str_pad(strval($date['hour']),2,'0',STR_PAD_LEFT);
		$date['i'] = str_pad(strval($date['minute']),2,'0',STR_PAD_LEFT);
		$date['s'] = str_pad(strval($date['second']),2,'0',STR_PAD_LEFT);
		$date['S'] = ($date['year'] < 0 ? '-' : '');
		$date['f'] = abs($date['year']);

		return str_replace(
			array('%d','%j','%m','%n','%Y','%y','%a','%A','%g','%h','%G','%H','%i','%s','%f','%S',
				'%F','%M','%l','%D'),
			array($date['d'],$date['day'],$date['m'],$date['month'],$date['Y'],
				$date['y'],$date['a'],$date['A'],$date['g'],$date['h'],
				$date['hour'],$date['H'],$date['i'],$date['s'],$date['f'],
				$date['S'],$date['F'],$date['M'],$date['l'],$date['D']),
			$format);
	}
}